package org.invigorare.auctions.comm;

import java.util.*;
import java.util.Map.Entry;

import org.invigorare.SetUtils;
import org.invigorare.auctions.comm.EntrySyncResult.Status;

/**
 * An object containing a list of Successes or Failure codes (EntrySyncStatus enums)
 * for entries synced for/from one database.
 * @author Jeff Chern
 *
 */

public class DbSyncResult {

	final String dbDisplayName;

	public static final int NO_ROW_ID = -1;
	List<Integer> rowIds;
	final List<EntrySyncResult> results;

	final Map<EntrySyncResult.Status, List<Integer>> rowIds_byStatus;
	final Map<EntrySyncResult.Status, List<RowResult>> rowResults_byStatus;
	private boolean rowIdsSet; // flag for lazy eval
	
	public class RowResult {
		final public int rowId;
		final public EntrySyncResult result;
		public RowResult(int rowId, EntrySyncResult result) { this.rowId = rowId; this.result = result; }
	}
	
	/**
	 * 
	 * @param dbDisplayName
	 * @param rowIds list of RowIds corresponding to each status. Can be null, in which case, constant NO_ROW_ID is associated with all statuses
	 * @param statuses list of statuses corresponding to each RowId. Cannot be null.
	 */
	public DbSyncResult(String dbDisplayName, List<Integer> rowIds, List<EntrySyncResult> statuses) {
		this.dbDisplayName = dbDisplayName;
		this.rowIds = rowIds;
		this.results = statuses;
		
		rowIds_byStatus = new HashMap<EntrySyncResult.Status, List<Integer>>(EntrySyncResult.Status.values().length);
		rowResults_byStatus = new HashMap<EntrySyncResult.Status, List<RowResult>>(EntrySyncResult.Status.values().length);
		rowIdsSet = false;
	}
	
	private void populateRowIdsByStatus() {
		for (EntrySyncResult.Status desiredStatus : EntrySyncResult.Status.values()) {
			
			// Rows having this status
			List<Integer> theseRowIds = new ArrayList<Integer>();
			List<RowResult> theseRowResults = new ArrayList<RowResult>();
			
			for (int i = 0; i < results.size(); i++) {
				EntrySyncResult result = results.get(i);

				if (result.status == desiredStatus) {
					int rowId = (this.rowIds != null) ? this.rowIds.get(i) : NO_ROW_ID;
					theseRowIds.add(rowId);
					theseRowResults.add(new RowResult(rowId, result));
				}
			}
			
			rowIds_byStatus.put(desiredStatus, theseRowIds);
			rowResults_byStatus.put(desiredStatus, theseRowResults);
			
		}
		rowIdsSet = true;
	}
	
	public List<Integer> getRowsIdsWithStatus(EntrySyncResult.Status status) {
		if (!rowIdsSet) populateRowIdsByStatus();
		return rowIds_byStatus.get(status);
	}
	
	public List<RowResult> getRowResultsWithStatus(EntrySyncResult.Status status) {
		if (!rowIdsSet) populateRowIdsByStatus();
		return rowResults_byStatus.get(status);
	}
	
	public List<Integer> getRowIdsWithOUTStatuses(EntrySyncResult.Status ... statusesToExclude) {
		
		List<Integer> outList = new ArrayList<Integer>();

		Set<Status> toUse = excludeStatuses(statusesToExclude);
		for (EntrySyncResult.Status s : toUse) {
			outList.addAll(getRowsIdsWithStatus(s));
		}
		return outList;
	}

	public List<RowResult> getRowResultsWithOUTStatuses(EntrySyncResult.Status ... statusesToExclude) {
		
		List<RowResult> outList = new ArrayList<RowResult>();

		Set<Status> toUse = excludeStatuses(statusesToExclude);
		for (EntrySyncResult.Status s : toUse) {
			outList.addAll(getRowResultsWithStatus(s));
		}
		return outList;
	}
	
	private Set<EntrySyncResult.Status> excludeStatuses(EntrySyncResult.Status ... statusesToExclude) {
		Set<EntrySyncResult.Status> statusesToUse = new SetUtils<EntrySyncResult.Status>().setDifference_ofArrays(
				EntrySyncResult.Status.values(),
				statusesToExclude
				);
		return statusesToUse;
	}
	
	/**
	 * Associates a list of rowIds with the list of statuses inside this object.
	 * The two lists must have the same number of elements, or a RuntimeException will be thrown.
	 * @param rowIds
	 * @return this object, for convenience
	 */
	public DbSyncResult associateRowIds(List<Integer> rowIds) {
		if (rowIds.size() != results.size()) {
			throw new RuntimeException("RowIds list must have same length as Statuses list (they are " + rowIds.size() + " and " + results.size() + " here respectively).");
		}
		
		this.rowIds = rowIds;
		rowIdsSet = false;
		return this;
	}
	
	public int getNumRows() {
		return results.size();
	}
	
	public boolean allSuccessfulOrNoConn() {
		int nRows = getNumRows();
		return (nRows == (getRowsIdsWithStatus(Status.SUCCESS).size() + getRowsIdsWithStatus(Status.NO_CONNECTION).size()));
	}
	
	/**
	 * Returns a string summary of this Result object, suitable for display.
	 * @return
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder();
		
		int nTotal = getNumRows();
		int nSuccessful = getRowsIdsWithStatus(Status.SUCCESS).size();
		int nErrors = nTotal - nSuccessful;
		
		sb.append(nSuccessful);
		sb.append(" entries from ");
		sb.append(dbDisplayName);
		
		if (nErrors != 0) {
			sb.append("\n        ");
			sb.append(nErrors);
			sb.append(" unsuccessful");
		}
		
		return sb.toString();
	}
}
